﻿using SCG.General;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TTEngine
{
    public abstract class Actor : MapObject
    {
        //constructor
        public Actor()
            : base()
        {
            CanCollapse = false;
            BlocksMovement = true;
            Body = 1;
            Mind = 0;
            Strength = 1;
            Dexterity = 1;
            Intellect = 1;
            Charm = 1;
            Movement = Globals.RandomIntFromTo(9, 11);
            Orders = OrderType.MoveRandomly;
            Alignment = "Neutral";
        }

        //movement
        public int Movement { get; set; }

        //basic alignment
        public string Alignment { get; set; }

        //is it hostile?
        public virtual bool IsHostile(Actor actor)
        {
            return Alignment != actor.Alignment;
        }

        //perform a test
        public bool PerformTest(Abilities ab, int difficulty, int exertion)
        {
            //TODO exertion
            return AbilityRoll(ab) >= difficulty;
        }

        //get current ability score
        public int AbilityScore(Abilities ab)
        {
            switch (ab)
            {
                case Abilities.Strength:
                    return CurrentStrength;
                case Abilities.Dexterity:
                    return CurrentDexterity;
                case Abilities.Intellect:
                    return CurrentIntellect;
                case Abilities.Charm:
                    return CurrentCharm;
                default:
                    return 0;
            }
        }

        //Make an ability roll
        public int AbilityRoll(Abilities ab)
        {
            int value = AbilityScore(ab);
            int sum = 0;
            while (value > 0)
            {
                sum += Globals.RandomIntFromTo(0, 3);
                value--;
            }

            //TODO modify for conditions
            return sum;
        }

        //find an adjacent enemy
        public Actor AdjacentEnemy
        {
            get
            {
                RandomList<Actor> candidates = null;
                for(int y = -1; y <= 1; y++)
                    for(int x = -1; x <= 1; x++)
                        if (x != 0 || y != 0)
                        {
                            Actor enemy = Level.GetActorAt(X + x, Y + y, false);
                            if (enemy != null && IsHostile(enemy))
                            {
                                if (candidates == null)
                                    candidates = new RandomList<Actor>();
                                candidates.Add(enemy);
                            }
                        }
                if (candidates != null)
                    return candidates.RandomValue;
                else
                    return null;
            }
        }

        //body & mind
        public int Body { get; set; }
        public int Mind { get; set; }
        public int DamageBody { get; set; }
        public int DamageMind { get; set; }
        public int CurrentBody
        {
            get
            {
                int b = Body - DamageBody;
                if (b < 0)
                    b = 0;
                return b;
            }
        }
        public int CurrentMind
        {
            get
            {
                int m = Mind - DamageMind;
                if (m < 0)
                    m = 0;
                return m;
            }
        }
        public bool WasKilled { get; set; }

        //delay between attacks
        public int AttackCounter { get; set; }

        //get weapon being used
        public MapObject Weapon
        {
            get
            {
                if (_readiedGear == null)
                    return null;
                foreach (MapObject wpn in _readiedGear)
                    if (wpn.IsWeapon)
                        return wpn;
                foreach (MapObject wpn in _readiedGear)
                    if (wpn.Damage != null)
                        return wpn;
                return null;
            }
        }

        //apply damage
        public virtual bool ApplyDamage(int damage, string damageType, string damageMsg)
        {
            if (WasKilled)
                return false;
            if (damage <= 0)
                return false;

            if (damageMsg != "")
                Level.Host.DisplayMessage(damageMsg, X, Y);
            DamageBody += damage;

            //dead?
            if (CurrentBody <= 0)
            {
                if (IsPC)
                {
                    //TODO unconcious
                }
                else
                    Kill();
            }
            else
            {
                //TODO wake up
            }

            return true;
        }

        //kill
        public void Kill()
        {
            if (WasKilled)
                return;
            WasKilled = true;
            Level.Host.DisplayMessage(DisplayName + " is killed!", X, Y);

            //drop stuff
            if (_gear != null)
            {
                for (int n = _gear.Count - 1; n >= 0; n--)
                {
                    MapObject mo = _gear[n];
                    RemoveGear(mo);
                    Level.AddObjectToMap(mo, X, Y);
                }
            }

            //remove from map
            Level.RemoveObjectFromMap(this);

            //if PC, remove from party
            if (IsPC)
            {
                Globals.Party.Members.Remove(this);
                //TODO de-select if selected

            }

            //TODO create blood stain
            //TODO produce corpse
        }

        //attributes
        public int Strength { get; set; }
        public int Dexterity { get; set; }
        public int Intellect { get; set; }
        public int Charm { get; set; }
        public int CurrentStrength
        {
            get
            {
                return Strength;
            }
        }
        public int CurrentDexterity
        {
            get
            {
                return Dexterity;
            }
        }
        public int CurrentIntellect
        {
            get
            {
                return Intellect;
            }
        }
        public int CurrentCharm
        {
            get
            {
                return Charm;
            }
        }

        //current movement rate
        public int CurrentMovement
        {
            get
            {
                int rate = Movement;

                //TODO immobilized?

                //you can carry half your max load without penalty
                float halfLoad = MaximumWeight / 2.0f;
                if (GearWeight > halfLoad)
                {
                    float loadOverHalfMax = GearWeight - halfLoad;
                    float pct = loadOverHalfMax / halfLoad;
                    float penaltyMult = 1.0f - pct;
                    rate = (int)(Movement * penaltyMult);
                    if (rate < 0)
                        rate = 0;
                }

                //apply conditions
                foreach (Condition c in Conditions)
                    rate = c.ProcessSpeed(rate);

                return rate;
            }
        }

        //public properties
        public OrderType Orders { get; set; }
        public int DestX { get; set; }
        public int DestY { get; set; }
        public Actor Target { get; set; }
        public SCGPoint Destination
        {
            get
            {
                return new SCGPoint(DestX, DestY);
            }
        }

        //is it a PC?
        public bool IsPC
        {
            get
            {
                return Globals.Party.Members.Contains(this);
            }
        }

        //total weight carried
        public float GearWeight
        {
            get
            {
                if (_gear == null)
                    return 0;
                float w = 0;
                foreach (MapObject mo in _gear)
                    w += mo.UnitWeight * mo.Quantity;
                return w;
            }
        }

        //maximum weight they can carry
        public float MaximumWeight
        {
            get
            {
                return 20 + CurrentStrength * 4;
            }
        }

        //gear
        public List<MapObject> Gear
        {
            get
            {
                if (_gear == null)
                    return _noGear;
                return _gear;
            }
        }

        public List<MapObject> ReadiedGear
        {
            get
            {
                if (_readiedGear == null)
                    return _noGear;
                return _readiedGear;
            }
        }
 
        public int HandsFree
        {
            get
            {
                int n = 2;
                if (_readiedGear != null)
                    foreach (MapObject gear in _readiedGear)
                        n -= gear.HandsRequired;
                return n;
            }
        }

        //give them an item of gear
        public void AddGear(MapObject mo)
        {
            if (_gear == null)
                _gear = new List<MapObject>();
            MapObject gear = GetGearWithTag(mo.ObjectName);
            if (gear != null && gear.CanCollapse)
                gear.Quantity += mo.Quantity;
            else
                _gear.Add(mo);
            mo.Owner = this;
        }
        public void RemoveGear(MapObject mo)
        {
            if (_gear == null)
                return;
            if (_gear.Contains(mo))
            {
                UnreadyGear(mo);
                _gear.Remove(mo);
            }
            mo.Owner = null;
        }

        //ready a piece of gear
        public void ReadyGear(MapObject mo)
        {
            if (!mo.CanReady)
                return;
            if (!HasGearWithTag(mo.ObjectName))
                AddGear(mo);
            if (_readiedGear == null)
                _readiedGear = new List<MapObject>();
            if (!_readiedGear.Contains(mo))
            {
                _readiedGear.Add(mo);
                mo.Readied();

                //enforce number of hands
                if (HandsFree < 0)
                {
                    for(int n = _readiedGear.Count - 1; n >= 0; n--)
                        if (_readiedGear[n].HandsRequired > 0)
                            if (_readiedGear[n] != mo)
                            {
                                UnreadyGear(_readiedGear[n]);
                                if (HandsFree >= 0)
                                    break;
                            }
                }
            }
        }
        public void UnreadyGear(MapObject mo)
        {
            if (_readiedGear == null)
                return;
            if (_readiedGear.Contains(mo))
            {
                mo.UnReadied();
                _readiedGear.Remove(mo);
            }
        }

        //get a piece of gear by tag
        public MapObject GetGearWithTag(string tag)
        {
            if (_gear == null)
                return null;
            foreach (MapObject mo in _gear)
                if (mo.HasTag(tag))
                    return mo;
            return null;
        }
        public bool HasGearWithTag(string tag)
        {
            return GetGearWithTag(tag) != null;
        }
        public MapObject GetReadiedGearWithTag(string tag)
        {
            if (_readiedGear == null)
                return null;
            foreach (MapObject mo in _readiedGear)
                if (mo.HasTag(tag))
                    return mo;
            return null;
        }
        public bool HasReadiedGearWithTag(string tag)
        {
            return GetReadiedGearWithTag(tag) != null;
        }

        //conditions
        public List<Condition> Conditions
        {
            get
            {
                if (_conditions == null)
                    return _noConditions;
                return _conditions;
            }
        }

        //do they have a certain condition?
        public bool HasCondition(string condName)
        {
            condName = condName.ToUpper();
            foreach (Condition c in Conditions)
                if (c.ConditionName.ToUpper() == condName)
                    return true;
            return false;
        }

        //has conditions?
        public bool HasConditions
        {
            get
            {
                if (_conditions == null)
                    return false;
                return _conditions.Count > 0;
            }
        }

        //give a condition
        public void AddCondition(string condName)
        {
            if (HasCondition(condName))
                return;
            if (_conditions == null)
                _conditions = new List<Condition>();
            Condition c = ConditionFactory.GetCondition(condName);
            _conditions.Add(c);
            c.ConditionAdded(this);
        }

        //remove a condition
        public void RemoveCondition(string condName)
        {
            condName = condName.ToUpper();
            if (!HasCondition(condName))
                return;
            for(int n = _conditions.Count - 1; n >= 0; n--)
                if (_conditions[n].ConditionName.ToUpper() == condName)
                {
                    Condition c = _conditions[n];
                    _conditions.Remove(c);
                    c.ConditionRemoved(this);
                    return;
                }
        }

        //is actor
        public override bool IsActor
        {
            get
            {
                return true;
            }
        }

        //how far can they throw something?
        public int ThrowRange(MapObject mo)
        {
            int range = (int)((CurrentStrength * 2 + 1) - mo.UnitWeight);
            if (mo.UnitWeight <= 1)
                if (range < 4)
                    range = 4;
            if (range < 2)
                range = 0;
            if (range > 4)
                range = 4;
            return range;
        }

        //can they throw this item?
        public bool CanThrow(MapObject mo)
        {
            return ThrowRange(mo) > 0;
        }

        //private members
        private List<MapObject> _gear = null;
        private List<MapObject> _readiedGear = null;
        private List<Condition> _conditions = null;
        private static List<MapObject> _noGear = new List<MapObject>();
        private static List<Condition> _noConditions = new List<Condition>();
    }
}
